package defectsim;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;

import javax.vecmath.Vector2d;

import repast.simphony.parameter.Parameters;
import repast.simphony.parameter.Schema;

import defectsim.agents.Dislocation;
import defectsim.agents.PointDefect;
import defectsim.resources.ExternalForce;

public class SimulationMetrics {

	private SimulationEnvironment environment = SimulationEnvironment.getInstance();

	private int annihilated;
	private int nucleated;
	private int pinned;
	private int mobile;
	private int dipole;
	private int total;
	private int mobilePercentage;
	private double strainRate;
	private double meanStrainRate;
	private double strain;
	private double energy;
	private double maxVelocity;
	private double minVelocity;
	private Vector2d meanVelocity;
	private Vector2d meanDistance;
	private Vector2d meanStress;
	private Vector2d rmsVelocity;
	private double minTimeStep;

	private String outputDirectory;
	private BufferedWriter tickData = null;

	public SimulationMetrics(){
		annihilated = 0;
		nucleated = 0;
		pinned = 0;
		mobile = 0;
		dipole = 0;
		total = 0;	
		meanVelocity = new Vector2d();
		meanDistance = new Vector2d();
		meanStress = new Vector2d();
		rmsVelocity = new Vector2d();
	}

	public double getMinTimeStep() {
		return minTimeStep;
	}

	public double getStrainRate() {
		return strainRate;
	}

	public double getStrain() {
		return strain;
	}

	public double getMeanStrainRate() {
		return meanStrainRate;
	}

	public int getAnnihilated() {
		return annihilated;
	}

	public void setAnnihilated(int annihilated) {
		this.annihilated = annihilated;
	}

	public int getNucleated() {
		return nucleated;
	}

	public void setNucleated(int nucleated) {
		this.nucleated = nucleated;
	}

	public double getMaxVelocity() {
		return maxVelocity;
	}
	
	public double getMeanVelocity() {
		return meanVelocity.x;
	}
	
	public double getRMSVelocity() {
		return rmsVelocity.x;
	}

	public double getMeanStress() {
		return meanStress.x;
	}
	
	public int getNoMobileDislocation(){
		return mobile;
	}
	
	public void update(Iterable<Dislocation> allDefects, double boxLength){
		Vector2d totalDistance = new Vector2d();
		Vector2d totalABSVelocity = new Vector2d();
		Vector2d totalVelocity = new Vector2d();
		Vector2d totalStress = new Vector2d();
		Vector2d totalRMSVelocity = new Vector2d();
		double totalEnergy = 0;
		double strainRate1 = 0;
		this.mobile = 0;
		this.dipole = 0;
		this.total = 0;	
		this.pinned = 0;
		double burgersVector = environment.getMaterial().getBurgersVector();

		for (Object defect : allDefects) {
			if (defect instanceof Dislocation){
				Dislocation dislocation = (Dislocation)defect;

				if(dislocation.isMobile()){
					mobile++;
					totalDistance.add(dislocation.getDistance());
					double vx = dislocation.getVelocity().x;
					double vy = dislocation.getVelocity().y;
					totalABSVelocity.add(new Vector2d(Math.abs(vx), Math.abs(vy)));
					totalVelocity.add(new Vector2d(vx, vy));
					totalRMSVelocity.add(new Vector2d(vx * vx, vy * vy));
					totalStress.add(dislocation.getEffectiveStress());
					totalEnergy += dislocation.getVelocity().x * dislocation.getVelocity().x;
					strainRate1 += dislocation.getUnitBurgersVector() * burgersVector * dislocation.getVelocity().x;

					if (dislocation.getVelocity().x > maxVelocity)
						maxVelocity = dislocation.getVelocity().x;
					if (dislocation.getVelocity().x < minVelocity)
						minVelocity = dislocation.getVelocity().x;
					if (dislocation.getDeltaTime() < minTimeStep)
						minTimeStep = dislocation.getDeltaTime();


				}

				if(dislocation.isDipole())
					dipole++;
				
				if(dislocation.isPinned())
					pinned++;
				
				total++;
			}
		}
		if (mobile != 0 && total != 0){
			mobilePercentage = (int)(100 * mobile / total);
//			meanVelocity = new Vector2d(totalVelocity.x / mobile, totalVelocity.y / mobile);
			meanVelocity = new Vector2d(totalABSVelocity.x / mobile, totalABSVelocity.y / mobile);
			rmsVelocity = new Vector2d(Math.sqrt(totalRMSVelocity.x / mobile), Math.sqrt(totalRMSVelocity.y / mobile));
			meanDistance = new Vector2d(totalDistance.x / mobile, totalDistance.y / mobile);
			meanStress = new Vector2d(totalStress.x / mobile, totalStress.x / mobile);	
			strainRate = totalABSVelocity.x * Globals.METER_TO_MICROMETER * (burgersVector * Globals.NANOMETER_TO_MICROMETER / (boxLength * boxLength));
			meanStrainRate = strainRate1 / mobile;
//			strain = totalDistance.x * (burgersVector * Globals.NANOMETER_TO_MICROMETER / (boxLength * boxLength));
			strain = meanDistance.x * (burgersVector * Globals.NANOMETER_TO_MICROMETER / (boxLength * boxLength));
			energy = totalEnergy;
		}

	}

	//	if ((int)ExternalForce.getInstance().getCycle() > cycle && saveData){
	//		cycle++;
	//		String s = "";
	//		try {
	//			s = s + Integer.toString(cycle) + " " + Double.toString(maxStrain) + " " + 
	//			Double.toString(minStrain) + " " + Double.toString(Math.abs(maxStrain) + Math.abs(minStrain)) + " " +
	//			Double.toString(minVelocity) + " " + Double.toString(maxVelocity) + " " + 
	//			Double.toString(Math.abs(maxVelocity) + Math.abs(minVelocity)) + " " +
	//			Double.toString(minDistance) + " " + Double.toString(maxDistance) + " " +
	//			Double.toString(Math.abs(maxDistance) + Math.abs(minDistance)) + " " +
	//			Double.toString(minEnergy) + " " + Double.toString(maxEnergy) + " " +
	//			Double.toString(Math.abs(maxEnergy) + Math.abs(minEnergy));
	//			s = s + "\n";
	//			cyclicData.write(s);
	//		}
	//		catch (IOException e) {
	//			// TODO Auto-generated catch block
	//			e.printStackTrace();
	//		}
	//		System.out.printf("Cycle: %d, Max: %f, Min: %f, Total: %f\n", cycle, maxStrain, minStrain, Math.abs(maxStrain) + Math.abs(minStrain));
	//		maxStrain = 0;
	//		minStrain = 0;
	//		maxVelocity = 0;
	//		minVelocity = 0;
	//		maxEnergy = 0;
	//		minEnergy = 0;
	//	}

	public void setFiles(){
		//		String userDir = System.getProperty("user.dir",".");
		String osName = System.getProperty("os.name");
		if (osName.contains("Windows"))
			outputDirectory = "D:\\My Java\\result\\";
		else
			outputDirectory = "/home/altug/graphs/";
		outputDirectory += "Run_" + Util.now() + "/";
		boolean success = (new File(outputDirectory)).mkdir();
		if (success)
			System.out.println(outputDirectory + " directory created successfully!");
		else
			System.out.println(outputDirectory + " directory couldn't be created!");
		//		String fname =  prefix + "cyclic_" + Util.now() + ".dat";
		//		try {
		//			cyclicData = new BufferedWriter(new FileWriter(fname));
		//		} catch (IOException e) {
		//			// TODO Auto-generated catch block
		//			e.printStackTrace();
		//		}
		String fname = outputDirectory + "tick.dat";
		try {
			tickData = new BufferedWriter(new FileWriter(fname));
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		
	}

	public void saveInputData(Parameters p) throws IOException{
		BufferedWriter inputData = null;
		String fname = outputDirectory + "inputs.dat";
		inputData = new BufferedWriter(new FileWriter(fname));

		Schema schema = p.getSchema();
		Iterable<String> params = schema.parameterNames();
		for(String pname : params){
			String line = pname +";" + p.getValueAsString(pname) + "\n";
			inputData.write(line);
		}
		inputData.close();
	}

	public void savePositionData(Iterable<Dislocation> allDefects, int tick) throws IOException{
		String positive = "\u02d4";
		String negative = "\u02d5";
		String alignment = "";
		BufferedWriter positionData = null;
		String fname = outputDirectory + "position_" + Integer.toString(tick) + ".dat";
		positionData = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(fname),"UTF8"));
		for (Object defect : allDefects) 
			if (defect instanceof Dislocation){
				Dislocation dislocation = (Dislocation)defect;
				alignment = dislocation.getUnitBurgersVector() > 0 ? positive : negative;
				String line = 	String.format("%f", dislocation.getX()) + ";" + 
				String.format("%f", dislocation.getY()) + ";" +
				String.format("%f", dislocation.getNetStress().x) + ";" +
				String.format("%f", dislocation.getNetStress().y) + ";" +
				String.format("%f", dislocation.getVelocity().x) + ";" +
				String.format("%f", dislocation.getVelocity().y) + ";" +
				String.format("%f", dislocation.getUnitBurgersVector()) + ";" +
				alignment + ";" +
				String.format("%f", dislocation.getDirection()) + ";" +
				String.format("%d", dislocation.isDipole() ? 1 : 0) + ";" +
				String.format("%d", dislocation.isPinned() ? 1 : 0) + ";" +
				String.format("%d", dislocation.isMobile() ? 1 : 0) + "\n";
				positionData.write(line);
			}
		positionData.close();
	}
	
	public void savePositionDataPointSource(Iterable<PointDefect> allDefects) throws IOException{
		String pd = "\u2022";
		BufferedWriter positionData = null;
		String fname = outputDirectory + "position_pointsource.dat";
		positionData = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(fname),"UTF8"));
		for (Object defect : allDefects) 
			if (defect instanceof PointDefect){
				PointDefect pointDefect = (PointDefect)defect;
				String line = 	String.format("%f", pointDefect.getX()) + ";" + 
				String.format("%f", pointDefect.getY()) + ";" +
				pd + "\n";
				positionData.write(line);
			}
		positionData.close();
	}

	public void printTickData(){

		System.out.printf("Tick: %d, # of Dislocation: %d, # Mobile: %d, Percent: %d, Annihilated: %d, Nucleated: %d, Dipole: %d, Pinned: %d\n",
				(int)environment.getCurrentTick(), 
				this.total, 
				this.mobile, 
				this.mobilePercentage, 
				this.annihilated, 
				this.nucleated, 
				this.dipole,
				this.pinned);

		System.out.printf("RMS velocity: %f m/s, Mean velocity: %f m/s, Mean distance: %f µm, Mean stress: %f MPa\n",
				this.rmsVelocity.x,
				this.meanVelocity.x, 
				this.meanDistance.x, 
				this.meanStress.x * Globals.GIGAPASCAL_TO_MEGAPASCAL);

		System.out.printf("Strain: %e, Strain rate: %f\n", 
				this.strain, 
				this.strainRate);
		//		System.out.println("External stress: " + ExternalForce.getInstance().getStress(null));
		System.out.println("--------------------------------------------------------------------------------------------------------------------------");

	}

	public void saveTickData(){

		String s = "";
		try {
			s = s + Integer.toString((int)environment.getCurrentTick()) + " " + 			// 1
			Double.toString(this.meanVelocity.x) + " " + 									// 2
			Double.toString(this.meanDistance.x) + " " + 									// 3
			Double.toString(this.energy) + " " + 											// 4
			Double.toString(this.strainRate) + " " +				 						// 5
			Double.toString(this.meanStress.x * Globals.GIGAPASCAL_TO_MEGAPASCAL) + " " +	// 6
			Double.toString(ExternalForce.getInstance().getStress(null).x) + " " +			// 7
			Double.toString(this.strain) + " " + 											// 8
			Integer.toString(this.total) + " " + 											// 9
			Integer.toString(this.mobile) + " " + 											// 10
			Integer.toString(this.annihilated) + " " + 										// 11
			Integer.toString(this.nucleated) + " " + 										// 12
			Double.toString(this.meanStrainRate) + " " +				 					// 13
			Integer.toString(this.dipole) + " " +											// 14
			Integer.toString(this.pinned) + " " + 											// 15
			Double.toString(this.rmsVelocity.x);											// 16

			s = s + "\n";
			tickData.write(s);
		}
		catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	public void closeFiles() throws IOException{
		tickData.close();
	}

}
